using Microsoft.Xrm.Client;
using Sustainalytics.DynamicsCRMGateway.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using AccountFeatureEnum = Sustainalytics.Auth.Claims.AccountFeature;

namespace Sustainalytics.DynamicsCRMGateway.Tests
{
    public class CrmTestDataHelper
    {
        private const string UnitTest = "_ UNIT TEST ";

        private readonly CrmServiceContext _crmServiceContext;

        private readonly Dictionary<int, Account> _accounts = new Dictionary<int, Account>();
        private readonly Dictionary<int, ProductUniverse> _productUniverses = new Dictionary<int, ProductUniverse>();
        private readonly Dictionary<int, AccountUniverse> _accountUniverses = new Dictionary<int, AccountUniverse>();
        private readonly Dictionary<int, CompaniesGroup> _companiesGroups = new Dictionary<int, CompaniesGroup>();
        private readonly Dictionary<int, CompaniesGroupConstituent> _companiesGroupConstituents = new Dictionary<int, CompaniesGroupConstituent>();
        private readonly Dictionary<int, Company> _companies = new Dictionary<int, Company>();

        private readonly HashSet<Tuple<int, int>> _accountWithProductUniverseAssociations = new HashSet<Tuple<int, int>>();
        private readonly HashSet<Tuple<int, IEnumerable<int>>> _accountUniverseWithCompaniesGroupsAssociations = new HashSet<Tuple<int, IEnumerable<int>>>();
        private readonly Dictionary<int, HashSet<int>> _universeWithFeaturesAssociations = new Dictionary<int, HashSet<int>>();
        private readonly HashSet<Tuple<int, int>> _companiesGroupWithCompanyAssociations = new HashSet<Tuple<int, int>>();

        public Dictionary<int, Account> Accounts { get { return _accounts; } }
        public Dictionary<int, Company> Companies { get { return _companies; } }

        public CrmTestDataHelper(CrmServiceContext crmServiceContext)
        {
            _crmServiceContext = crmServiceContext;
        }

        public static T CreateEntity<T>(int index) where T : CrmEntity, INameable, new()
        {
            T entity = new T() { Id = Guid.NewGuid(), Name = GetTestName(index) };
            if (entity is Company)
            {
                (entity as Company).CapitalIqId = GetTestName(index);
            }
            return entity;
        }

        public static string GetTestName(int index)
        {
            return UnitTest + index.ToString().PadLeft(2, ' ');
        }

        public CrmTestDataHelper AddAccountWithProductUniverse(int account, int productUniverse)
        {
            _accounts.AddEntityIfNotExists(account);
            _productUniverses.AddEntityIfNotExists(productUniverse);
            _accountUniverses.AddEntity(_accountUniverses.Count + 1);
            _accountWithProductUniverseAssociations.Add(Tuple.Create(account, productUniverse));
            return this;
        }

        public CrmTestDataHelper AddAccountWithFeatureAndUniverses(int account, int feature, IEnumerable<int> universes)
        {
            foreach (var universe in universes)
            {
                AddAccountWithProductUniverse(account, universe);
                _universeWithFeaturesAssociations.AddToSet(universe, feature);
            }
            return this;
        }

        public CrmTestDataHelper AddCompaniesGroupWithCompanies(int companiesGroup, IEnumerable<int> companies)
        {
            _companiesGroups.AddEntityIfNotExists(companiesGroup);
            _companiesGroups[companiesGroup].IsStandard = false;
            foreach (var company in companies)
            {
                _companies.AddEntityIfNotExists(company);
                _companiesGroupConstituents.AddEntityIfNotExists(_companiesGroupConstituents.Count + 1);
                _companiesGroupWithCompanyAssociations.Add(Tuple.Create(companiesGroup, company));
            }
            return this;
        }

        public CrmTestDataHelper AddAccountWithUniverseAndCompaniesGroups(int account, int universe, IEnumerable<int> companiesGroups)
        {
            AddAccountWithProductUniverse(account, universe);
            _accountUniverseWithCompaniesGroupsAssociations.Add(Tuple.Create(_accountUniverses.Last().Key, companiesGroups));
            foreach (var companiesGroup in companiesGroups)
            {
                _companiesGroups.AddEntityIfNotExists(companiesGroup);
                if (_companiesGroups[companiesGroup].IsStandard == null)
                {
                    _companiesGroups[companiesGroup].IsStandard = true;
                }
            }
            return this;
        }

        public void Create()
        {
            Create(_accounts);
            Create(_productUniverses);
            Create(_accountUniverses);
            Create(_companiesGroups);
            Create(_companies);
            Create(_companiesGroupConstituents);

            AssociateAccountsWithProductUniverses();
            AssociateProductUniversesWithUniverseFeatures();
            AssociateAccountsWithCompaniesGroups();
            AssociateCompaniesGroupsWithCompanies();
        }

        public void Delete()
        {
            Delete<Account>();
            Delete<ProductUniverse>();
            Delete<AccountUniverse>();
            Delete<CompaniesGroup>();
            Delete<Company>();
            Delete<CompaniesGroupConstituent>();
        }

        private void AssociateAccountsWithProductUniverses()
        {
            if (!_accountWithProductUniverseAssociations.Any())
                return;

            var index = 1;
            foreach (var accountWithProductUniverse in _accountWithProductUniverseAssociations)
            {
                _accountUniverses[index].Account = _accounts[accountWithProductUniverse.Item1];
                _accountUniverses[index].ProductUniverse = _productUniverses[accountWithProductUniverse.Item2];
                index++;
            }

            Update(_accountUniverses);
        }

        private void AssociateProductUniversesWithUniverseFeatures()
        {
            if (!_universeWithFeaturesAssociations.Any())
                return;

            var universeFeatures = new CrmRepository<UniverseFeature>(_crmServiceContext).ReadAll().ToDictionary(x => x.Name);
            foreach (var universeWithFeature in _universeWithFeaturesAssociations)
            {
                _productUniverses[universeWithFeature.Key].UniverseFeatures = universeWithFeature.Value
                    .Select(feature => universeFeatures[Enum.GetName(typeof(AccountFeatureEnum), feature)])
                    .ToList();
            }

            Update(_productUniverses);
        }

        private void AssociateAccountsWithCompaniesGroups()
        {
            if (!_accountUniverseWithCompaniesGroupsAssociations.Any())
                return;

            foreach (var accountUniverseWithCompaniesGroups in _accountUniverseWithCompaniesGroupsAssociations)
            {
                _accountUniverses[accountUniverseWithCompaniesGroups.Item1].CompaniesGroups = accountUniverseWithCompaniesGroups.Item2
                    .Select(companiesGroup => _companiesGroups[companiesGroup])
                    .ToList();
            }

            Update(_accountUniverses);
        }

        private void AssociateCompaniesGroupsWithCompanies()
        {
            if (!_companiesGroupWithCompanyAssociations.Any())
                return;

            var index = 1;
            foreach (var companiesGroupWithCompany in _companiesGroupWithCompanyAssociations)
            {
                _companiesGroupConstituents[index].CompaniesGroup = _companiesGroups[companiesGroupWithCompany.Item1];
                _companiesGroupConstituents[index].Company = _companies[companiesGroupWithCompany.Item2];
                index++;
            }

            Update(_companiesGroupConstituents);
        }

        private void Create<T>(Dictionary<int, T> entities) where T : CrmEntity
        {
            if (!entities.Any())
                return;

            new CrmRepository<T>(_crmServiceContext).Create(entities.Values.ToList());
        }

        private void Update<T>(Dictionary<int, T> entities) where T : CrmEntity
        {
            if (!entities.Any())
                return;

            new CrmRepository<T>(_crmServiceContext).Update(entities.Values.ToList());
        }

        private void Delete<T>() where T : CrmEntity, INameable, new()
        {
            var repository = new CrmRepository<T>(_crmServiceContext);
            var ids = repository.ReadAll().Where(x => x.Name != null && x.Name.StartsWith(UnitTest)).Select(x => x.Id).ToList();

            if (ids.Count == 0)
                return;

            repository.Delete(ids);
        }
    }
}
